﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="AuthorizationModule.cs" company="Anton Frattaroli">
//   Copyright (c) Anton Frattaroli. All rights reserved.
// </copyright>
// <summary>
//   Defines the AuthorizationModule implementation class of the IHttpModule interface to authorize access to requests.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace Lidocaine
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.SqlClient;
    using System.DirectoryServices;
    using System.Globalization;
    using System.Web;
    
    /// <summary>
    /// Defines the AuthorizationModule class.
    /// </summary>
    public class AuthorizationModule : IHttpModule
    {
        #region Methods
        /// <summary>
        /// Adds event handlers to the application lifecycle.
        /// </summary>
        /// <param name="context">The HttpApplication object for the request.</param>
        public virtual void Init(HttpApplication context)
        {
            context.AuthorizeRequest += new EventHandler(this.AuthorizationHandler);
        }

        /// <summary>
        /// Implements the standard Dispose() method of the IHttpModule interface.
        /// </summary>
        public virtual void Dispose()
        {
        }

        /// <summary>
        /// Tests permission levels against the database.
        /// </summary>
        /// <param name="user">The user to authorize.</param>
        /// <param name="path">The HTTP request path.</param>
        /// <returns>Returns a bool value indicating whether or not the user has permissions on the path.</returns>
        private static bool HasPermission(Profile user, string path)
        {
            bool hasPermission = false;
            if (path.EndsWith(".SETUP", StringComparison.OrdinalIgnoreCase))
            {
                hasPermission = true;
            }
            else if (path.EndsWith(".PREVIEW", StringComparison.OrdinalIgnoreCase))
            {
                IList<string> roles = Profile.Current.Roles;
                if (roles.Contains("Site Administrator") || roles.Contains("Content Manager"))
                {
                    hasPermission = true;
                }
            }
            else if (path.EndsWith(".ASHX", StringComparison.OrdinalIgnoreCase))
            {
                hasPermission = true;
            }
            else
            {
                using (SqlConnection conn = new SqlConnection(Environmental.ConnectionString))
                {
                    conn.Open();
                    string[] pathNodes = path.ToUpperInvariant().Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    string reconstructedPath = string.Empty;
                    DataTable permissionItems;
                    for (int i = 0; i < pathNodes.Length; i++)
                    {
                        permissionItems = new DataTable();
                        permissionItems.Locale = CultureInfo.InvariantCulture;
                        hasPermission = false;
                        if (reconstructedPath.Length == 0)
                        {
                            reconstructedPath += pathNodes[i];
                        }
                        else
                        {
                            reconstructedPath += "/" + pathNodes[i];
                        }

                        if (reconstructedPath == "WEBRESOURCE.AXD")
                        {
                            hasPermission = true;
                        }
                        else if (reconstructedPath.EndsWith(".ASPX", StringComparison.OrdinalIgnoreCase))
                        {
                            // Page not in the "CMS" directory
                            using (SqlCommand cmd = new SqlCommand("Lidocaine.GetPagePermissionsWithGroupInfo", conn))
                            {
                                cmd.CommandType = CommandType.StoredProcedure;
                                cmd.Parameters.AddWithValue("@PagePath", reconstructedPath);
                                using (SqlDataReader rdr = cmd.ExecuteReader())
                                {
                                    if (rdr.HasRows)
                                    {
                                        permissionItems.Load(rdr);
                                    }
                                    else
                                    {
                                        HttpContext.Current.Response.Redirect(Environmental.ErrorUrl + "?Code=404");
                                    }
                                }
                            }

                            hasPermission = AuthorizationModule.CheckPermissions(user, permissionItems);
                        }
                        else if (reconstructedPath.Contains(".") && !reconstructedPath.StartsWith("CMS/", StringComparison.OrdinalIgnoreCase) && !reconstructedPath.EndsWith(".AXD", StringComparison.OrdinalIgnoreCase))
                        {
                            // File not in the "CMS" directory
                            using (SqlCommand cmd = new SqlCommand("Lidocaine.GetFilePermissionsWithGroupInfo", conn))
                            {
                                cmd.CommandType = CommandType.StoredProcedure;
                                cmd.Parameters.AddWithValue("@VirtualPath", reconstructedPath);
                                using (SqlDataReader rdr = cmd.ExecuteReader())
                                {
                                    if (rdr.HasRows)
                                    {
                                        permissionItems.Load(rdr);
                                    }
                                    else
                                    {
                                        HttpContext.Current.Response.Redirect(Environmental.ErrorUrl + "?Code=404");
                                    }
                                }
                            }

                            hasPermission = AuthorizationModule.CheckPermissions(user, permissionItems);
                        }
                        else if (!reconstructedPath.StartsWith("CMS/", StringComparison.OrdinalIgnoreCase) && reconstructedPath != "CMS" && !reconstructedPath.Contains("."))
                        {
                            // Directory that is not the "CMS" directory
                            using (SqlCommand cmd = new SqlCommand("Lidocaine.GetFolderPermissionsWithGroupInfo", conn))
                            {
                                cmd.CommandType = CommandType.StoredProcedure;
                                cmd.Parameters.AddWithValue("@FolderPath", reconstructedPath);
                                using (SqlDataReader rdr = cmd.ExecuteReader())
                                {
                                    if (rdr.HasRows)
                                    {
                                        permissionItems.Load(rdr);
                                    }
                                    else
                                    {
                                        HttpContext.Current.Response.Redirect(Environmental.ErrorUrl + "?Code=404");
                                    }
                                }
                            }

                            hasPermission = AuthorizationModule.CheckPermissions(user, permissionItems);
                        }
                        else if (reconstructedPath.StartsWith("CMS/", StringComparison.OrdinalIgnoreCase) || reconstructedPath == "CMS")
                        {
                            // File in the "CMS" directory, or the "CMS" directory itself
                            Profile currentUser = Profile.Current;
                            IList<string> roles = currentUser.Roles;
                            if (roles.Contains("Content Provider") || roles.Contains("Content Manager") || roles.Contains("Site Administrator"))
                            {
                                hasPermission = true;
                            }
                        }

                        if (!hasPermission)
                        {
                            break;
                        }
                    }
                }
            }

            return hasPermission;
        }

        /// <summary>
        /// Authorizes access for the user against the request's permissions list.
        /// </summary>
        /// <param name="user">The user to authorize.</param>
        /// <param name="permissionItems">The list of permissions for the request.</param>
        /// <returns>A boolean value indicating whether or not the user is authorized.</returns>
        private static bool CheckPermissions(Profile user, DataTable permissionItems)
        {
            bool hasPermission = false;
            foreach (DataRow dr in permissionItems.Rows)
            {
                if (dr["PermissionAllowType"].ToString() == "True")
                {
                    if (dr["ADGroup"].ToString() == "False")
                    {
                        if (dr["GroupName"].ToString() == "Anonymous Users")
                        {
                            hasPermission = true;
                        }
                        else if (dr["GroupName"].ToString() == "Authenticated Users" && !user.IsAnonymous)
                        {
                            hasPermission = true;
                        }
                        else if (user.Roles.Contains(dr["GroupName"].ToString()))
                        {
                            hasPermission = true;
                        }
                    }
                }
                else if (Convert.ToInt32(dr["PermissionAllowID"], CultureInfo.InvariantCulture) == user.UserId)
                {
                    hasPermission = true;
                }
            }

            return hasPermission;
        }

        /// <summary>
        /// Authorizes requests.
        /// </summary>
        /// <param name="sender">The HttpApplication object for the request.</param>
        /// <param name="e">The EventArgs object for the request.</param>
        private void AuthorizationHandler(object sender, EventArgs e)
        {
            HttpApplication app = (HttpApplication)sender;
            Profile currentUser = Profile.Current;
            string requestedPath = app.Request.Path;
            string requestedQuery = app.Request.Url.Query.TrimStart('?');
            if (!AuthorizationModule.HasPermission(currentUser, requestedPath))
            {
                if (currentUser.IsAnonymous)
                {
                    app.Context.Response.Redirect(Environmental.LogOnUrl + "?ReturnUrl=" + HttpUtility.UrlEncode(requestedPath + (!string.IsNullOrEmpty(requestedQuery) ? "?" + requestedQuery : string.Empty)));
                    app.CompleteRequest();
                }
                else
                {
                    app.Context.Response.Redirect(Environmental.ErrorUrl + "?Code=401");
                    app.CompleteRequest();
                }
            }
        }
        #endregion
    }
}